"""Alibaba Cloud image moderation helper."""

from typing import Any, Dict, List, Optional

import logging
import time

from django.conf import settings

from alibabacloud_imageaudit20191230.client import Client as ImageAuditClient
from alibabacloud_imageaudit20191230 import models as image_audit_models
from alibabacloud_tea_openapi import models as open_api_models
from alibabacloud_tea_util import models as tea_models


logger = logging.getLogger(__name__)


class ImageAuditUtil:
    """封装阿里云内容安全 ImageAudit SDK 的同步审核工具。

    该工具负责：
    - 读取 Django settings 中的审核配置
    - 初始化并缓存 SDK Client
    - 调用 `ScanImage` 接口审核指定 URL 的图片
    - 将审核结果整理为统一的字典结构返回
    """

    def __init__(self, scenes: Optional[List[str]] = None):
        # 从 settings 读取审核配置，允许调用方覆盖默认检测场景
        cfg = getattr(settings, "ALIYUN_IMAGE_AUDIT", {}) or {}

        # SDK 所需的访问凭证与服务端点
        self.access_key_id = cfg.get("ACCESS_KEY_ID") or getattr(settings, "ALIBABA_CLOUD_ACCESS_KEY_ID", None)
        self.access_key_secret = cfg.get("ACCESS_KEY_SECRET") or getattr(settings, "ALIBABA_CLOUD_ACCESS_KEY_SECRET", None)
        self.endpoint = cfg.get("ENDPOINT", "imageaudit.cn-shanghai.aliyuncs.com")
        self.scenes = self._normalize_scenes(scenes or cfg.get("SCENES", ["porn", "terrorism", "ad"]))
        self.connect_timeout = int(cfg.get("CONNECT_TIMEOUT", 10000))  # 默认 10 秒
        self.read_timeout = int(cfg.get("READ_TIMEOUT", 15000))  # 默认 15 秒
        self.retry_times = int(cfg.get("RETRY_TIMES", 2))
        self.retry_interval = float(cfg.get("RETRY_INTERVAL", 1.0))

        if not self.access_key_id or not self.access_key_secret:
            raise ValueError("未配置阿里云内容安全访问凭证，请在 settings.ALIYUN_IMAGE_AUDIT 中设置 ACCESS_KEY_ID/ACCESS_KEY_SECRET")

        self._client = self._create_client()

    def _create_client(self) -> ImageAuditClient:
        """构造并返回阿里云内容安全 Client。"""
        config = open_api_models.Config(
            access_key_id=self.access_key_id,
            access_key_secret=self.access_key_secret,
            endpoint=self.endpoint,
            region_id="cn-shanghai",
        )
        return ImageAuditClient(config)

    @staticmethod
    def _normalize_scenes(raw_scenes: Any) -> List[str]:
        """将任意格式的场景参数转换为字符串列表。"""
        if not raw_scenes:
            return ["porn", "terrorism", "ad"]
        if isinstance(raw_scenes, str):
            return [scene.strip() for scene in raw_scenes.split(",") if scene.strip()]
        return list(raw_scenes)

    def audit_image(self, image_url: str) -> Dict[str, Any]:
        """调用阿里云审核指定 URL 图片。

        Args:
            image_url: OSS 或公网可访问的图片地址。

        Returns:
            标准化的审核结果字典。
        """
        if not image_url:
            return self._build_result(passed=True, suggestion="pass")

        try:
            # 构建单张图片的审核任务
            task = image_audit_models.ScanImageRequestTask()
            task.image_url = image_url

            # 创建请求对象
            request = image_audit_models.ScanImageRequest()
            request.task = [task]
            
            # 尝试多种方式设置 Scene 参数，确保被正确序列化
            # 根据阿里云 SDK，Scene 参数应该是字符串列表
            scene_set = False
            
            # 方法1: 尝试直接设置 scene 属性（小写）
            if hasattr(request, "scene"):
                request.scene = self.scenes
                scene_set = True
                logger.debug("使用 request.scene 设置场景参数: %s", self.scenes)
            
            # 方法2: 尝试设置 Scene 属性（大写）
            if not scene_set and hasattr(request, "Scene"):
                request.Scene = self.scenes
                scene_set = True
                logger.debug("使用 request.Scene 设置场景参数: %s", self.scenes)
            
            # 方法3: 尝试设置 scenes 属性（复数）
            if not scene_set and hasattr(request, "scenes"):
                request.scenes = self.scenes
                scene_set = True
                logger.debug("使用 request.scenes 设置场景参数: %s", self.scenes)
            
            # 方法4: 使用 setattr 动态设置
            if not scene_set:
                try:
                    setattr(request, "Scene", self.scenes)
                    scene_set = True
                    logger.debug("使用 setattr 设置 Scene 属性: %s", self.scenes)
                except Exception:
                    pass
            
            # 方法5: 直接设置到 __dict__（最后手段）
            if not scene_set:
                request.__dict__["Scene"] = self.scenes
                request.__dict__["scene"] = self.scenes
                logger.debug("使用 __dict__ 设置场景参数: %s", self.scenes)
            
            # 验证请求参数（调试用）
            if hasattr(request, "to_map"):
                try:
                    request_map = request.to_map()
                    logger.debug("请求参数映射: %s", request_map)
                    # 确保 Scene 在映射中
                    if "Scene" not in request_map and "scene" not in request_map:
                        logger.warning("警告: Scene 参数未在请求映射中找到，强制添加")
                        request_map["Scene"] = self.scenes
                except Exception as e:
                    logger.warning("无法获取请求映射: %s", e)

            runtime = tea_models.RuntimeOptions()
            runtime.read_timeout = self.read_timeout
            runtime.connect_timeout = self.connect_timeout

            attempts = max(self.retry_times, 0) + 1
            last_exc: Optional[Exception] = None
            for attempt in range(attempts):
                try:
                    response = self._client.scan_image_with_options(request, runtime)
                    body = response.body
                    last_exc = None
                    break
                except Exception as exc:  # pylint: disable=broad-except
                    last_exc = exc
                    logger.warning(
                        "图片审核请求失败（第 %s 次尝试/%s）: %s",
                        attempt + 1,
                        attempts,
                        exc,
                    )
                    if attempt < attempts - 1:
                        time.sleep(self.retry_interval * (attempt + 1))

            if last_exc is not None:
                raise last_exc

            # 容错解析结果列表
            task_results = self._extract_task_results(body)
            if not task_results:
                logger.warning("图片审核未返回检测结果: %s", body.to_map() if hasattr(body, "to_map") else body)
                return self._build_result(
                    passed=False,
                    suggestion="review",
                    details=[{"message": "审核结果为空", "scene": "unknown"}],
                )

            return self._parse_results(task_results, body)

        except Exception as exc:  # pylint: disable=broad-except
            logger.exception("调用阿里云图片审核失败: %s", exc)
            return self._build_result(
                passed=False,
                suggestion="review",
                details=[{"message": str(exc), "scene": "exception"}],
            )

    @staticmethod
    def _extract_task_results(body: Any) -> Optional[List[Any]]:
        if hasattr(body, "data") and hasattr(body.data, "results"):
            return body.data.results
        if hasattr(body, "results"):
            return body.results
        if hasattr(body, "data") and hasattr(body.data, "tasks"):
            tasks = body.data.tasks
            if tasks:
                first = tasks[0]
                if hasattr(first, "results"):
                    return first.results
        if hasattr(body, "to_map"):
            data_map = body.to_map()
            return data_map.get("Data", {}).get("Results") or data_map.get("Results")
        return None

    def _parse_results(self, task_results: List[Any], raw_body: Any) -> Dict[str, Any]:
        """根据 SDK 返回的结果对象组装统一结构。"""
        details: List[Dict[str, Any]] = []
        suggestion = "pass"

        for result in task_results:
            scene = getattr(result, "scene", None) or getattr(result, "Scene", "unknown")
            label = getattr(result, "label", None) or getattr(result, "Label", "")
            confidence = getattr(result, "confidence", None) or getattr(result, "Confidence", None)
            current_suggestion = getattr(result, "suggestion", None) or getattr(result, "Suggestion", "pass")

            if current_suggestion and current_suggestion.lower() != "pass":
                details.append(
                    {
                        "scene": scene,
                        "label": label,
                        "confidence": confidence,
                        "suggestion": current_suggestion,
                    }
                )
                suggestion = self._prioritize_suggestion(suggestion, current_suggestion)

        passed = suggestion == "pass"
        return self._build_result(
            passed=passed,
            suggestion=suggestion,
            details=details,
            raw=raw_body.to_map() if hasattr(raw_body, "to_map") else raw_body,
        )

    @staticmethod
    def _prioritize_suggestion(current: str, new_value: str) -> str:
        """遵循 block > review > pass 的优先级合并建议。"""
        order = {"block": 3, "review": 2, "pass": 1}
        current = (current or "pass").lower()
        new_value = (new_value or "pass").lower()
        return new_value if order.get(new_value, 0) >= order.get(current, 0) else current

    @staticmethod
    def _build_result(
        *, passed: bool, suggestion: str, details: Optional[List[Dict[str, Any]]] = None, raw: Any = None
    ) -> Dict[str, Any]:
        """格式化审核结果。

        由于 SDK 响应对象不一定可序列化，此处将未知类型强制转为字符串，
        以便在 API 响应中安全返回。
        """
        if isinstance(raw, (dict, list, str, int, float, bool)) or raw is None:
            raw_payload = raw
        else:
            raw_payload = str(raw)
        return {
            "passed": passed,
            "suggestion": suggestion,
            "details": details or [],
            "raw": raw_payload,
        }